/*******************************************************************************
Copyright (c) 2016 Advanced Micro Devices, Inc. 

All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, 
this list of conditions and the following disclaimer in the documentation 
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/

#include "XSbench_header.h"

#ifdef DOMPI
#include<mpi.h>
#endif

// Prints program logo
void logo(int version)
{
  border_print();
  printf(
	 "                   __   __ ___________                 _                        \n"
	 "                   \\ \\ / //  ___| ___ \\               | |                       \n"
	 "                    \\ V / \\ `--.| |_/ / ___ _ __   ___| |__                     \n"
	 "                    /   \\  `--. \\ ___ \\/ _ \\ '_ \\ / __| '_ \\                    \n"
	 "                   / /^\\ \\/\\__/ / |_/ /  __/ | | | (__| | | |                   \n"
	 "                   \\/   \\/\\____/\\____/ \\___|_| |_|\\___|_| |_|                   \n\n"
	 );
  border_print();
  center_print("Developed at Argonne National Laboratory", 79);
  char v[100];
  sprintf(v, "Version: %d", version);
  center_print(v, 79);
  border_print();
}

// Prints Section titles in center of 80 char terminal
void center_print(const char *s, int width)
{
  int length = strlen(s);
  int i;
  for (i=0; i<=(width-length)/2; i++) {
    fputs(" ", stdout);
  }
  fputs(s, stdout);
  fputs("\n", stdout);
}

void print_results( Inputs in, int mype, double runtime, int nprocs,
	unsigned long long vhash )
{
  // Calculate Lookups per sec
  int lookups_per_sec = (int) ((double) in.lookups / runtime);
	
  // If running in MPI, reduce timing statistics and calculate average
#ifdef DOMPI
  int total_lookups = 0;
  MPI_Barrier(MPI_COMM_WORLD);
  MPI_Reduce(&lookups_per_sec, &total_lookups, 1, MPI_INT,
	     MPI_SUM, 0, MPI_COMM_WORLD);
#endif
	
  // Print output
  if( mype == 0 )
    {
      border_print();
      center_print("RESULTS", 79);
      border_print();

      // Print the results
      printf("Threads:     %d\n", in.nthreads);
#ifdef DOMPI
      printf("MPI ranks:   %d\n", nprocs);
#endif
#ifdef DOMPI
      printf("Total Lookups/s:            ");
      fancy_int(total_lookups);
      printf("Avg Lookups/s per MPI rank: ");
      fancy_int(total_lookups / nprocs);
#else
      printf("Runtime:     %.3lf seconds\n", runtime);
      printf("Lookups:     "); fancy_int(in.lookups);
      printf("Lookups/s:   ");
      fancy_int(lookups_per_sec);
#endif
#ifndef VERIFICATION
      printf("Non-zero Check: %llu\n", vhash);
#else
      printf("Verification checksum: %llu\n", vhash);
#endif
      border_print();

      // For bechmarking, output lookup/s data to file
      if( SAVE )
	{
	  FILE * out = fopen( "results.txt", "a" );
	  fprintf(out, "%d\t%d\n", in.nthreads, lookups_per_sec);
	  fclose(out);
	}
    }
}

void print_inputs(Inputs in, int nprocs, int version )
{
  // Calculate Estimate of Memory Usage
  int mem_tot = estimate_mem_usage( in );
  logo(version);
  center_print("INPUT SUMMARY", 79);
  border_print();
#ifdef VERIFICATION
  printf("Verification Mode:            on\n");
#endif
  printf("Materials:                    %d\n", 12);
  printf("H-M Benchmark Size:           %s\n", in.HM);
  printf("Total Nuclides:               %ld\n", in.n_isotopes);
  printf("Gridpoints (per Nuclide):     ");
  fancy_int(in.n_gridpoints);
  printf("Unionized Energy Gridpoints:  ");
  fancy_int(in.n_isotopes*in.n_gridpoints);
  printf("XS Lookups:                   "); fancy_int(in.lookups);
#ifdef DOMPI
  printf("MPI Ranks:                    %d\n", nprocs);
  printf("OMP Threads per MPI Rank:     %d\n", in.nthreads);
  printf("Mem Usage per MPI Rank (MB):  "); fancy_int(mem_tot);
#else
  printf("Threads:                      %d\n", in.nthreads);
  printf("Est. Memory Usage (MB):       "); fancy_int(mem_tot);
#endif
  border_print();
  center_print("INITIALIZATION", 79);
  border_print();
}

void border_print(void)
{
  printf(
	 "==================================================================="
	 "=============\n");
}

// Prints comma separated integers - for ease of reading
void fancy_int( long a )
{
  if( a < 1000 )
    printf("%ld\n",a);

  else if( a >= 1000 && a < 1000000 )
    printf("%ld,%03ld\n", a / 1000, a % 1000);

  else if( a >= 1000000 && a < 1000000000 )
    printf("%ld,%03ld,%03ld\n",a / 1000000,(a % 1000000) / 1000,a % 1000 );

  else if( a >= 1000000000 )
    printf("%ld,%03ld,%03ld,%03ld\n",
	   a / 1000000000,
	   (a % 1000000000) / 1000000,
	   (a % 1000000) / 1000,
	   a % 1000 );
  else
    printf("%ld\n",a);
}

void print_CLI_error(void)
{
  printf("Usage: ./XSBench <options>\n");
  printf("Options include:\n");
  printf("  -t <threads>     Number of OpenMP threads to run\n");
  printf("  -s <size>        Size of H-M Benchmark to run (small, large, XL, XXL)\n");
  printf("  -g <gridpoints>  Number of gridpoints per nuclide (overrides -s defaults)\n");
  printf("  -l <lookups>     Number of Cross-section (XS) lookups\n");
  printf("Default is equivalent to: -s large -l 15000000\n");
  printf("See readme for full description of default run values\n");
  exit(4);
}

Inputs read_CLI( int argc, char * argv[] )
{
  Inputs input;
	
  // defaults to max threads on the system
  input.nthreads = omp_get_num_procs();
	
  // defaults to 355 (corresponding to H-M Large benchmark)
  input.n_isotopes = 355;
	
  // defaults to 11303 (corresponding to H-M Large benchmark)
  input.n_gridpoints = 11303;
	
  // defaults to 15,000,000
  input.lookups = 15000000;
	
  // defaults to H-M Large benchmark
  input.HM = (char *) malloc( 6 * sizeof(char) );
  input.HM[0] = 'l' ; 
  input.HM[1] = 'a' ; 
  input.HM[2] = 'r' ; 
  input.HM[3] = 'g' ; 
  input.HM[4] = 'e' ; 
  input.HM[5] = '\0';
	
  // Check if user sets these
  int user_g = 0;
	
  // Collect Raw Input
  for( int i = 1; i < argc; i++ )
    {
      char * arg = argv[i];

      // nthreads (-t)
      if( strcmp(arg, "-t") == 0 )
	{
	  if( ++i < argc )
	    input.nthreads = atoi(argv[i]);
	  else
	    print_CLI_error();
	}
      // n_gridpoints (-g)
      else if( strcmp(arg, "-g") == 0 )
	{	
	  if( ++i < argc )
	    {
	      user_g = 1;
	      input.n_gridpoints = atol(argv[i]);
	    }
	  else
	    print_CLI_error();
	}
      // lookups (-l)
      else if( strcmp(arg, "-l") == 0 )
	{
	  if( ++i < argc )
	    input.lookups = atoi(argv[i]);
	  else
	    print_CLI_error();
	}
      // HM (-s)
      else if( strcmp(arg, "-s") == 0 )
	{	
	  if( ++i < argc )
	    input.HM = argv[i];
	  else
	    print_CLI_error();
	}
      else
	print_CLI_error();
    }

  // Validate Input

  // Validate nthreads
  if( input.nthreads < 1 )
    print_CLI_error();
	
  // Validate n_isotopes
  if( input.n_isotopes < 1 )
    print_CLI_error();
	
  // Validate n_gridpoints
  if( input.n_gridpoints < 1 )
    print_CLI_error();

  // Validate lookups
  if( input.lookups < 1 )
    print_CLI_error();
	
  // Validate HM size
  if( strcasecmp(input.HM, "small") != 0 &&
      strcasecmp(input.HM, "large") != 0 &&
      strcasecmp(input.HM, "XL") != 0 &&
      strcasecmp(input.HM, "XXL") != 0 )
    print_CLI_error();
	
  // Set HM size specific parameters
  // (defaults to large)
  if( strcasecmp(input.HM, "small") == 0 )
    input.n_isotopes = 68;
  else if( strcasecmp(input.HM, "XL") == 0 && user_g == 0 )
    input.n_gridpoints = 238847; // sized to make 120 GB XS data
  else if( strcasecmp(input.HM, "XXL") == 0 && user_g == 0 )
    input.n_gridpoints = 238847 * 2.1; // 252 GB XS data

  // Return input struct
  return input;
}
